﻿using Modbus.Device;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ModbusRTUDevice
{
    public class NetDevice
    {
        private UdpClient client;
        private ModbusSerialMaster master;
        private System.Timers.Timer poll_timer; //用于轮训设备状态
        private Task connection_task; //用于判断连接状态以及是否重连
        public string ServerIp { get; set; }
        public byte SlaveAddress { get; set; } // 从站站号
        public int ServerPort { get; set; }
        public int ReadTimeout { get; set; }
        public bool Connected { get; set; } = false;
        public ushort[] RxRegisters { get; private set; }
        public bool[] ReadCoils { get; private set; }
        public bool[] Inputs { get; private set; }
        public bool[] OutputCoils { get; set; }
        public ushort RegisterAdress { get; set; } // 保持寄存器，功能码03
        public ushort ReadCoilsAddress { get; set; }
        public ushort RegisterLength { get; set; }
        public ushort InputAddress { get; set; }
        public ushort InputLength { get; set; } // 读离散输入寄存器，功能码02
        public ushort ReadCoilsLength { get; set; } // 对应功能码01，读输入线圈
        public ushort WriteCoilAddress { get; set; } // 写线圈，功能码0F
        public event Action<ushort[]> OnGetHoldingRegisters;
        public event Action<bool[]> OnGetCoils;
        public event Action<bool[]> OnGetInputs;
        public bool Disposed { get; set; } = false;
        public bool PollTaskReady { get; private set; } = false;

        public class Message
        {
            public ushort Address { get; set; }
            public ushort[] Regs { get; set; }
        }

        public Queue<Message> TxQueue = new Queue<Message>();
        public int MaxQueueSize = 5;  // 发送队列长度限制

        public NetDevice(string ip, int port, ushort slaveaddress = 1,int interval = 100, int timeout = 200)
        {
            client = new UdpClient();
            ServerIp = ip;
            ServerPort = port;
            SlaveAddress = (byte)slaveaddress;
            poll_timer = new System.Timers.Timer()
            {
                AutoReset = true,
                Interval = interval,
                Enabled = false
            };
            ReadTimeout = timeout;
            // UDP
            client.Connect(ServerIp, ServerPort);
            Connected = true;
            // TCP 需要配置
            //client.ReceiveTimeout = ReadTimeout;
            //client.NoDelay = true;
            //client.SendTimeout = timeout;
        }

        private UdpClient CreateClient()
        {
            var ret = new UdpClient();            
            // ret.ReceiveTimeout = ReadTimeout            
            return ret;
        }

        public void Begin()
        {
            if (!(connection_task is null))
            {
                throw new Exception("System Already Started.");
            }

            master = ModbusSerialMaster.CreateRtu(client);
            master.Transport.Retries = 0;
            master.Transport.ReadTimeout = 500;

            Task.Factory.StartNew(poll_task, TaskCreationOptions.LongRunning);

#if TCP
            connection_task = Task.Factory.StartNew(() =>
            {
                while (true)
                {
                    if (!Connected && !Disposed)
                    {
                        try
                        {
                            if (client is null)
                            {
                                client = CreateClient();
                                master = ModbusSerialMaster.CreateRtu(client);
                            }
                            client.Connect(ServerIp, ServerPort);
                            Console.WriteLine($"Connect {ServerIp}:{ServerPort} Successfully.");
                            Connected = true;
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine($"{ServerIp}: " + ex.Message);
                            CloseTcp();
                        }
                    }
                    Thread.Sleep(1000);
                    if (Disposed)
                        break;
                }
            }, TaskCreationOptions.LongRunning);
#endif
        }

        public void CloseTcp()
        {
            Connected = false;
            if (!(client is null))
            {
                client?.Close();
                client?.Dispose();
            }
            client = null;
        }

        public void Dispose()
        {
            Disposed = true;
            CloseTcp();
            poll_timer.Stop();
            poll_timer.Dispose();
        }

        public void SendRegistersAsync(Message msg)
        {
            TxQueue.Enqueue(msg);
        }

        public void SendRegisters(Message msg)
        {
            master.WriteMultipleRegisters(SlaveAddress, msg.Address, msg.Regs);
        }

        private void poll_task()
        {
            int err_cnt = 0;
            while (true)
            {
#if TCP
            if (!Connected)
                return;
#endif

                if (ReadCoilsLength != 0)
                {
                    try
                    {
                        ReadCoils = master.ReadCoils(SlaveAddress, ReadCoilsAddress, ReadCoilsLength);
                        OnGetCoils?.Invoke(ReadCoils);
                        err_cnt = 0;
                        if(PollTaskReady == false)
                        {
                            ReadCoils.CopyTo(OutputCoils, 0);
                        }
                        PollTaskReady = true;
                    }
                    catch (Exception ex)
                    {
                        err_cnt++;
                        Console.WriteLine($"{ServerIp}: {ex.Message}");
                    }
                }

                Thread.Sleep(100);

                try
                {
                    if (OutputCoils != null && PollTaskReady)
                    {
                        master.WriteMultipleCoils(SlaveAddress, WriteCoilAddress, OutputCoils);
                        err_cnt = 0;
                    }
                }
                catch (Exception ex)
                {
                    err_cnt++;
                    Console.WriteLine($"{ServerIp}: {ex.Message}");
                }             

                Thread.Sleep(100);
                Connected = err_cnt < 10;                
            }
        }
    }
}
